/**
  * @ignore
  * BEGIN HEADER
  *
  * Contains:        WYSIWYG Plugin
  * CVM-Role:        CodeMirror Plugin
  * Maintainer:      Hendrik Erz
  * License:         GNU GPL v3
  *
  * Description:     This plugin enables a certain amount of WYSIWYG on the
  *                  editor. NOTE: This plugin is unsupported and experimental.
  *
  * END HEADER
  */

import { commands } from 'codemirror'
import { getWysiwygRE } from '@common/regular-expressions'

// WYSIWYG means you need HTML elements rendered instead of
// the underlying Markdown code
import md2html from '@common/util/md-to-html'

const wysiwygRE = getWysiwygRE()

commands.markdownWYSIWYG = function (cm) {
  let match

  // We'll only render the viewport
  const viewport = cm.getViewport()
  for (let i = viewport.from; i < viewport.to; i++) {
    if (cm.getModeAt({ 'line': i, 'ch': 0 }).name !== 'markdown') continue
    // Always reset lastIndex property, because test()-ing on regular
    // expressions advance it.
    wysiwygRE.lastIndex = 0

    // First get the line and test if the contents contain a link
    let line = cm.getLine(i)
    if (!wysiwygRE.test(line)) {
      continue
    }

    wysiwygRE.lastIndex = 0 // Necessary because of global flag in RegExp

    // Run through all elements in this line
    while ((match = wysiwygRE.exec(line)) != null) {
      // Now get the precise beginning of the match and its end
      let curFrom = { 'line': i, 'ch': match.index }
      let curTo = { 'line': i, 'ch': match.index + match[0].length }

      let cur = cm.getCursor('from')
      if (cur.line === curFrom.line && cur.ch >= curFrom.ch && cur.ch <= curTo.ch) {
        // Cursor is in selection: Do not render.
        continue
      }

      // We can only have one marker at any given position at any given time
      if (cm.findMarks(curFrom, curTo).length > 0) continue

      // Let's find out what kind of element we should create
      let elemName = ''
      let className = ''
      let styles = ['display: inline'] // Make sure the elements are always inline
      if (match[1]) elemName = className = 'strong'
      if (match[2]) elemName = className = 'em'
      if (match[3]) elemName = className = 'strong'
      if (match[4]) elemName = className = 'em'
      if (match[5] && match[6]) {
        elemName = 'h' + match[5].length
        className = 'heading'
        styles.push('font-size: inherit')
      }
      if (match[7]) { elemName = 'blockquote'; className = 'quote' }
      if (match[8]) { elemName = 'code'; className = 'comment' }

      // We also need the text contents
      let contents = match[1] || match[2] || match[3] || match[4] || match[6] || match[7] || match[8]

      let elem = document.createElement(elemName)
      // The inner HTML contains the contents inside the element. We'll also
      // render any Markdown inside the element so that it looks nice.
      // We need to remove the containing paragraphs!
      elem.innerHTML = md2html(contents).replace(/<p>(.*?)<\/p>/g, '$1')
      elem.style = styles.join('; ') // Add the styles
      elem.className = 'cm-' + className // Apply the corresponding class
      // Apply TextMarker
      try {
        let textMarker = cm.markText(
          curFrom, curTo,
          {
            'clearOnEnter': true,
            'replacedWith': elem,
            'inclusiveLeft': false,
            'inclusiveRight': false
          }
        )

        elem.onclick = (e) => {
          textMarker.clear()
          cm.setCursor(cm.coordsChar({ 'left': e.clientX, 'top': e.clientY }))
          cm.focus()
        }
      } catch (err) {
        // CodeMirror throws errors if one tries to paper over an existing
        // mark with a new marker. In this case, don't mark the text and simply
        // do nothing.
        console.error(err)
      }
    }
  }
}
